No description has been provided for this image

M2.891 · Aprendizaje automático · PEC1

2024-2 · Máster universitario en Ciencia de datos (Data science)

Estudios de Informática, Multimedia y Telecomunicación

 

PEC 1: Preparación de datos

El objetivo principal de esta primera PEC es que os familiaricéis con el entorno de trabajo que vais a utilizar en el resto de prácticas de la asignatura. Dicho entorno estará formado por un conjunto de dependencias a los módulos de Python necesarios para poder ejecutar de forma correcta el código que resuelve vuestra PEC. Estas dependencias las gestionaremos gracias a la ayuda de Anaconda, que, entre otras cosas, nos provee de un gestor de entornos virtuales para Python.

Otra de las herramientas fundamentales del que será vuestro nuevo entorno de trabajo será Jupyter, que os permitirá trabajar con Notebooks (ficheros *.ipynb), como el presente enunciado, y donde podréis ejecutar vuestro código celda a celda, mostrando los resultados intermedios que necesitéis para comprender correctamente qué es lo que estáis haciendo en cada momento.

En esta primera PEC, otro de los aspectos imprescindibles que vamos a cubrir, tal y como adelanta su título, es el de la preparación de los datos. En esta PEC aprenderemos a cargar distintos conjuntos de datos o datasets, los combinaremos y nos ayudaremos de herramientas de visualización para comprender mejor cómo se distribuye el dato, con el objetivo de entender cómo podemos sacarle partido. Además, nos habituaremos a trabajar con conjuntos de entrenamiento y test para confirmar si las conclusiones que sacamos sobre una parte de las muestras se pueden generalizar y extrapolar al resto.

En resumen, en esta práctica veremos cómo aplicar diferentes técnicas para la carga y preparación de datos siguiendo los pasos listados a continuación:

  1. Carga y combinación de los distintos conjuntos de datos (2 puntos)
    1.1. Real Estate Valuation
    1.2. Taiwan Points of Interest (OpenStreetMap Export)
  2. Análisis de los datos (2.5 puntos)
    2.1. Análisis estadístico básico
    2.2. Análisis exploratorio de los datos
  3. Preprocesado de los datos (1 punto)
  4. Reducción de la dimensionalidad (2 puntos)
  5. Conjuntos desbalanceados de datos (1.5 puntos)
  6. Búsqueda y combinación de nuevos conjuntos de datos (1 punto)

Importante: cada uno de los ejercicios puede suponer varios minutos de ejecución, por lo que la entrega debe hacerse en formato notebook y en formato html, donde se vea el código, los resultados y comentarios de cada ejercicio. Se puede exportar el notebook a html desde el menú File $\to$ Download as $\to$ HTML.

Importante: existe un tipo de celda especial para albergar texto. Este tipo de celda os será muy útil para responder a las diferentes preguntas teóricas planteadas a lo largo de cada PEC. Para cambiar el tipo de celda a este tipo, elegid en el menú: Cell $\to$ Cell Type $\to$ Markdown.

Importante: la solución planteada no debe utilizar métodos, funciones o parámetros declarados "deprecated" en futuras versiones.

Importante: es conveniente que utilicéis una semilla con un valor fijo (en este Notebook se os propone la variable seed inicializada a 100) en todos aquellos métodos o funciones que contengan alguna componente aleatoria para aseguraros de que obtendréis siempre el mismo resultado en las distintas ejecuciones de vuestro código.

Importante: no olvidéis poner vuestro nombre y apellidos en la siguiente celda.

Nombre y apellidos: Pablo Perez Verdugo

Para la realización de la práctica necesitaremos installar e importar los siguientes módulos:

In [1]:
!pip install smogn==0.1.2
Requirement already satisfied: smogn==0.1.2 in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (0.1.2)
Requirement already satisfied: numpy in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from smogn==0.1.2) (2.2.3)
Requirement already satisfied: pandas in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from smogn==0.1.2) (2.2.3)
Requirement already satisfied: tqdm in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from smogn==0.1.2) (4.67.1)
Requirement already satisfied: python-dateutil>=2.8.2 in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from pandas->smogn==0.1.2) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from pandas->smogn==0.1.2) (2025.1)
Requirement already satisfied: tzdata>=2022.7 in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from pandas->smogn==0.1.2) (2025.1)
Requirement already satisfied: colorama in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from tqdm->smogn==0.1.2) (0.4.6)
Requirement already satisfied: six>=1.5 in c:\users\pablo\desktop\master uoc\aprendizaje automático\pec1\pec1_venv\lib\site-packages (from python-dateutil>=2.8.2->pandas->smogn==0.1.2) (1.17.0)
In [2]:
import numpy as np
import pandas as pd
import geopandas as gpd

from sklearn import preprocessing
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.neighbors import BallTree
import smogn

import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns

import folium

pd.set_option('display.max_columns', None)
seed = 100

%matplotlib inline

1. Carga y combinación de los distintos conjuntos de datos (2 puntos)¶

En esta PEC trabajaremos con un conjunto de datos en el que cada muestra representará la venta de un inmueble de la ciudad de Nueva Taipéi (Taiwán). Este conjunto de datos contiene una serie de columnas que nos dará información de la transacción y de algunas características de la vivienda. Sin embargo, queremos enriquecer dicho conjunto de datos añadiéndole un nuevo atributo descriptivo y esto lo haremos utilizando un segundo dataset, en el que cada muestra será un punto de interés de Taiwán, para calcular cuántos lugares de interés hay próximos a cada propiedad.

Comencemos con la carga de datos:

1.1. Real Estate Valuation¶

El primer conjunto de datos, que además es el principal, se llama Real Estate Valuation y es uno de los datasets disponibles dentro del Repositorio de Apdendizaje automático de la Universidad de California en Irvine.

En el enlace https://archive.ics.uci.edu/dataset/477/real+estate+valuation+data+set tenéis disponible tanto el mencionado dataset Real Estate Valuation como toda la información relevante para comprender mejor con qué tipo de dato vamos a trabajar. Tal y como se ha avanzado, las muestras de este conjunto de datos están relacionadas con ventas de inmuebles en la ciudad de Nueva Taipéi (Taiwán).

En primer lugar, deberéis cargar en el Notebook el conjunto de datos con el que trabajaremos durante el resto de la PEC. Para ello podéis descargarlo manualmente del enlace referido previamente, aunque os aconsejamos que instaléis y utilicéis el módulo ucimlrepo tal y como se explica en la propia página del dataset.

Nota: junto al enunciado de la PEC hemos adjuntado el fichero real+estate+valuation+data+set.zip que podéis utilizar en el caso de que la página citada previamente no responda.

Ejercicio: cargad el conjunto de datos "Real Estate Valuation" y mostrad:
  • El número y nombre de los atributos descriptivos (variables que podrían ser usadas para predecir la variable objetivo "y").
  • El número de filas (muestras) del conjunto de datos.
  • Verificad si hay o no "missing values" y en qué columnas.

Sugerencia: si usáis ucimlrepo, explorad los atributos metadata y variables del objeto obtenido.

Sugerencia: separad el conjunto de datos original en las variables "X" (atributos descriptivos) e "y" (variable objetivo), aunque quizá pueda serte de utilidad en algún punto tenerlos también en un único DataFrame combinados.

In [3]:
from ucimlrepo import fetch_ucirepo 
  
# fetch dataset 
real_estate_valuation = fetch_ucirepo(id=477) 
  
# data (as pandas dataframes) 
X = real_estate_valuation.data.features 
y = real_estate_valuation.data.targets 
  
# # metadata 
# print(real_estate_valuation.metadata) 
  
# # variable information 
# print(real_estate_valuation.variables) 
In [4]:
print(f"Nuestro dataset tiene {X.shape[0]} filas y {X.shape[1]} columnas")
print(f"El nombre de los atributos descriptivos son: {list(X.columns)}")
Nuestro dataset tiene 414 filas y 6 columnas
El nombre de los atributos descriptivos son: ['X1 transaction date', 'X2 house age', 'X3 distance to the nearest MRT station', 'X4 number of convenience stores', 'X5 latitude', 'X6 longitude']
In [5]:
# Combinar ambos df para facilitarme los posteriores analisis
X_y = pd.concat([X, y], axis = 1)
In [6]:
X_y.isnull().sum() # Ninguna columna tiene missing values
Out[6]:
X1 transaction date                       0
X2 house age                              0
X3 distance to the nearest MRT station    0
X4 number of convenience stores           0
X5 latitude                               0
X6 longitude                              0
Y house price of unit area                0
dtype: int64
Pregunta: ¿el conjunto de datos propuesto es un problema de aprendizaje automático supervisado o no?, en el caso de serlo, ¿de qué tipo de aprendizaje supervisado estaríamos hablando?

Sí, es un problema de aprendizaje automático supervisado ya que tenemos una variable que queremos predecir. Concretamente estamos ante un problema de regresión.

1.2. Taiwan Points of Interest (OpenStreetMap Export)¶

El segundo conjunto de datos, Taiwan Points of Interest (OpenStreetMap Export), complementa al conjunto de datos inicial con la información de los puntos de interés presentes en Taiwán.

La combinación de ambos conjuntos de datos la haremos mediante sus coordenadas geográficas. En nuestro caso, partiendo de la premisa de que lugares con un mayor número de puntos de interés cercanos podrían ser más caros, nos interesa calcular cuántos puntos de interés existen próximos a cada uno de los inmuebles que se han vendido.

La búsqueda de los puntos de interés más cercanos a un inmueble puede hacerse por medio de fuerza bruta, recorriendo todos los puntos de interés para cada uno de los inmuebles. Sin embargo, este método no es precisamente el más eficiente, por lo que para el desarrollo de esta PEC os proponemos la utilización de la estructura de datos BallTree. Este tipo de árbol es una estructura de datos de partición espacial utilizada para organizar puntos en un espacio multidimensional. La estructura BallTree divide los puntos del conjunto de datos en un conjunto anidado de bolas, tal y como se puede observar en el siguiente artículo: https://medium.com/@geethasreemattaparthi/ball-tree-and-kd-tree-algorithms-a03cdc9f0af9.

El conjunto de datos se ha obtenido del siguiente enlace: https://data.humdata.org/dataset/hotosm_twn_points_of_interest, y os lo facilitamos junto al enunciado en el fichero hotosm_twn_points_of_interest_points_geojson.geojson.

Ejercicio: calcula el número de puntos de interés a menos de 100 metros de distancia de cada inmueble y añádelo como un atributo descriptivo más:
  • Cargad el conjunto de datos "Taiwan Points of Interest (OpenStreetMap Export)" utilizando el método read_file de geopandas.
  • Inicializad una estructura de datos de tipo BallTree con los puntos de interés para posteriormente hacer búsquedas en ella.
  • Calculad el número de puntos de interés próximos a cada inmueble del primer conjunto de datos para añadírselo después como una nueva columna.

Nota: la latituda y longitud de cada lugar de interés viene almacenada en la columna "geometry" del conjunto de datos en una estructura llamada POINT, formada por los campos "y" (latitud) y "x" (longitud).

Nota: dado que la tierra es más esférica que plana, es conveniente utilizar la métrica de distancia "haversine" para el BallTree además de trabajar con radianes.

Pista: para algunos cálculos será necesario utilizar el radio de la Tierra en metros.

In [7]:
# Leemos nuestro dataset con POIs de Taiwan
gdf_twn_pois = gpd.read_file("hotosm_twn_points_of_interest_points_geojson-1.geojson")
In [8]:
# Creamos un mapa en folium para visualizar mejor las tiendas y los POIs
taiwan_map = folium.Map(location=[23.6978, 120.9605], zoom_start=8)

# Cambiamos el fondo a CartoDB Positron para mayor claridad
folium.TileLayer('CartoDB positron').add_to(taiwan_map)

poi_sample = gdf_twn_pois.sample(5000, random_state=seed)
for _, poi in poi_sample.iterrows():
    folium.CircleMarker(
        location=[poi.geometry.y, poi.geometry.x],  
        radius=2,
        color='red',
        fill=True,
        fill_color='red',
        fill_opacity=0.5
    ).add_to(taiwan_map)

for _, house in X.iterrows():
    folium.Marker(
        location=[house['X5 latitude'], house['X6 longitude']],  
        popup=f"House: {house['X5 latitude']}, {house['X6 longitude']}",
        icon=folium.Icon(color='blue', icon='home')
    ).add_to(taiwan_map)

taiwan_map
Out[8]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [9]:
# Extraemos las coordenadas de la columna geometry
coords = np.array([[geom.y, geom.x] for geom in gdf_twn_pois.geometry])

# Creamos el BallTree [1]
ball_tree = BallTree(np.radians(coords), metric='haversine')

# Extraemos las coordenadas de las casas en un array numpy convertido a radianes
house_coords = np.radians(X_y[['X5 latitude', 'X6 longitude']].to_numpy())

# Definimos el radio en m
radius_m = 500                   # Consideramos proximos los POIs a menos de 500 m
radius_rad = radius_m / 6371000  # Convertimos m a radianes

# Usamos el metodo query_radius para contar los POIs dentro del radio
X_y['X7 num_pois_cercanos'] = ball_tree.query_radius(house_coords, r=radius_rad, count_only=True)

Bibliografia [1].

2. Análisis de los datos (2.5 puntos)¶

En este apartado visualizaremos cada una de las columnas o features del dato para comprender mejor qué distribución tiene.

2.1. Análisis estadístico básico¶

Ejercicio: realizad un análisis estadístico básico:
  • Calculad estadísticos descriptivos básicos: media, mediana, desviación estandard, ...
  • Haced un histograma para cada variable.
Sugerencia: podéis usar la librería "pandas" y sus funciones "describe" y "value_counts", así como las funciones "bar", "hist" y "hist2d" de matplotlib (esta última os vendrá bien para mostrar un mapa de calor combinando las latitudes con las longitudes).
In [10]:
X_y.describe()
Out[10]:
X1 transaction date X2 house age X3 distance to the nearest MRT station X4 number of convenience stores X5 latitude X6 longitude Y house price of unit area X7 num_pois_cercanos
count 414.000000 414.000000 414.000000 414.000000 414.000000 414.000000 414.000000 414.000000
mean 2013.148971 17.712560 1083.885689 4.094203 24.969030 121.533361 37.980193 176.067633
std 0.281967 11.392485 1262.109595 2.945562 0.012410 0.015347 13.606488 133.448162
min 2012.667000 0.000000 23.382840 0.000000 24.932070 121.473530 7.600000 0.000000
25% 2012.917000 9.025000 289.324800 1.000000 24.963000 121.528085 27.700000 50.000000
50% 2013.167000 16.100000 492.231300 4.000000 24.971100 121.538630 38.450000 176.500000
75% 2013.417000 28.150000 1454.279000 6.000000 24.977455 121.543305 46.600000 320.000000
max 2013.583000 43.800000 6488.021000 10.000000 25.014590 121.566270 117.500000 490.000000
In [11]:
print(f"Las medianas son: \n{X_y.median()}")
Las medianas son: 
X1 transaction date                       2013.16700
X2 house age                                16.10000
X3 distance to the nearest MRT station     492.23130
X4 number of convenience stores              4.00000
X5 latitude                                 24.97110
X6 longitude                               121.53863
Y house price of unit area                  38.45000
X7 num_pois_cercanos                       176.50000
dtype: float64
In [12]:
# Excluimos las columnas de latitud y longitud ya que su histograma no nos aporta nada. Estas variables es mejor visualizarlas en el mapa que plotee previamente.
columns = ['X1 transaction date', 'X2 house age', 'X3 distance to the nearest MRT station',
           'X4 number of convenience stores','X7 num_pois_cercanos', 'Y house price of unit area']
# Creamos histogramas para cada columna
plt.figure(figsize=(15, 12))
for i, col in enumerate(columns):
    plt.subplot(2, 3, i+1)
    sns.histplot(X_y[col], bins=25)
    plt.title(f'Histogram of {col}')
plt.tight_layout()
plt.show()
No description has been provided for this image
In [13]:
# Voy a plotear este heatmap porque lo pide el enunciado, pero en mi opinion es mucho mejor visualizar la distribucion de lat/long con un mapa de folium
plt.figure(figsize=(8, 6))
plt.hist2d(X_y['X6 longitude'], X_y['X5 latitude'], bins=(25, 25), cmap='coolwarm')
plt.colorbar(label='Count')
plt.xlabel('Longitud')
plt.ylabel('Latitud')
plt.show()
No description has been provided for this image
Análisis: comentad los resultados.

De los histogramas ploteados y las metricas obtenidas podemos extraer las siguientes conclusiones:

  • No tenemos informacion de ventas de todos los meses -> seguramente el dataset que nos han facilitado es un sample de otro mas completo
  • La casa mas antigua vendida tenia casi 40 años, siendo la media de 17.7 años
  • La mitad de las casas vendidas tenian una stacion MRT a menos de 500 m
  • No todas las casas tienen una convenience store cerca
  • Vemos que la cantidad de POIs cercanos es muy variable y no sigue una distribucion normal
  • El precio adimensionalizado de las casas sigue una distribucion normal a excepcion de unos outliers con precio superior a 100

2.2. Análisis exploratorio de los datos¶

En este subapartado exploraremos gráficamente la relación de los atributos descriptivos con la variable objetivo y analizaremos las diferentes correlaciones.

Ejercicio: usando una librería gráfica, como por ejemplo matplotlib, para cada una de las variables descriptivas, mostrad la distribución de ésta respecto a la variable objetivo, poniendo, por ejemplo, en el eje X los valores de la variable descriptiva y en el eje Y los de la objetivo.

La finalidad de este ejercicio es la de identificar de manera visual y rápida si algunos atributos nos permiten predecir mejor que otros el valor de la variable objetivo.

Sugerencia: se recomienda utilizar regplot, de la biblioteca seaborn, ya que además de la distribución de puntos que se os pide os mostrará si hay alguna tendencia lineal positiva o negativa calculando una regresión lineal en cada caso.

Sugerencia: de manera adicional, es conveniente ver la distribución geoespacial de los precios de las viviendas, por lo que se recomienda realizar un scatter plot con una escala de colores adecuada relacionada con el precio.

No analizaremos latitud y longitud para predecir el precio de una vivienda porque la relación entre ubicación y precio es más espacial que lineal.

In [14]:
variables_descriptivas = ['X1 transaction date', 'X2 house age', 'X3 distance to the nearest MRT station',
                    'X4 number of convenience stores', 'X7 num_pois_cercanos']

plt.figure(figsize=(15, 12))
for i, col in enumerate(variables_descriptivas):
    plt.subplot(2, 3, i+1)
    sns.regplot(x=X_y[col], y=X_y['Y house price of unit area'], scatter_kws={'alpha':0.4}, line_kws={'color': 'red'})
    plt.title(f'{col} vs Y house price of unit area')
plt.tight_layout()
plt.show()
No description has been provided for this image
In [15]:
plt.figure(figsize=(8, 6))
sc = plt.scatter(X_y['X6 longitude'], X_y['X5 latitude'], c=X_y['Y house price of unit area'], cmap='coolwarm', alpha=0.7)
plt.colorbar(sc, label='Precio de vivienda por unidad de area')
plt.xlabel('Longitud')
plt.ylabel('Latitud')
plt.title('Distribución geoespacial de los precios de las viviendas')
plt.show()
No description has been provided for this image
Análisis:
Mirando las gráficas, ¿qué atributos parecen tener mayor influencia en el valor final de la variable objetivo? ¿Crees que con estos atributos sería suficiente para poder determinar el precio del inmueble?

Los atributos con mayor influencia en el valor final de la variable objetivo son 'X3 distance to the nearest MRT station', 'X4 number of convenience stores' y 'X7 num_pois_cercanos' y creo que si son suficientes. Otra medida importante es el tamaño de la casa pero en nuestro caso no nos afecta ya que la variable a predecir que es el precio de venta de la casa esta expresada en 3030 $New Taiwan Dollar/m^2$

Ejercicio: identifica y elimina los valores atípicos del conjunto de datos y repite las gráficas realizadas en el ejercicio anterior para analizar cómo podrían estar afectandonos este tipo de muestras.

Es muy importante que este paso lo realicéis en una copia del dato para no perder los valores atípicos del conjunto con el que estamos trabajando, ya que continuaremos trabajando con todas las muestras del dataset original en los siguientes apartados.

Nota: puedes utilizar, por ejemplo, el método IQR (Interquartile Range).

In [16]:
# Creamos una copia, para no perder nuestro df original
X_y_no_outliers = X_y.copy()

# Creamos una funcion para eliminar outliers basandonos en el IQR
def eliminar_outliers_iqr(df, columns):
    Q1 = df[columns].quantile(0.25)
    Q3 = df[columns].quantile(0.75)
    IQR = Q3 - Q1
    min_threshold = Q1 - 1.5 * IQR
    max_threshold = Q3 + 1.5 * IQR

    return df[~((df[columns] < min_threshold) | (df[columns] > max_threshold)).any(axis=1)]

X_y_no_outliers = eliminar_outliers_iqr(X_y_no_outliers, variables_descriptivas + ['Y house price of unit area'])
In [17]:
# Repetimos nuestros plots
plt.figure(figsize=(15, 12))
for i, col in enumerate(variables_descriptivas):
    plt.subplot(2, 3, i+1)
    sns.regplot(x=X_y_no_outliers[col], y=X_y_no_outliers['Y house price of unit area'], scatter_kws={'alpha':0.4}, line_kws={'color': 'red'})
    plt.title(f'{col} vs Y house price of unit area')
plt.tight_layout()
plt.show()
No description has been provided for this image
In [18]:
plt.figure(figsize=(8, 6))
sc = plt.scatter(X_y_no_outliers['X6 longitude'], X_y_no_outliers['X5 latitude'], c=X_y_no_outliers['Y house price of unit area'], cmap='coolwarm', alpha=0.7)
plt.colorbar(sc, label='Precio de vivienda por unidad de area')
plt.xlabel('Longitud')
plt.ylabel('Latitud')
plt.title('Distribución geoespacial de los precios de las viviendas')
plt.show()
No description has been provided for this image
Análisis: explica qué ocurre.

Ha cambiado el eje y ya que al eliminar outliers ya no va desde 0 a 120 sino que ahora va desde 0 hasta 70. Respecto a la tendencia de las lineas de regresion no ha cambiado ya que estos outliers no afectaban mucho.

Ejercicio: calculad y mostrad la correlación entre las variables numéricas.
In [19]:
# Para ello ploteamos la matrix de correlacion
corr_matrix = X_y_no_outliers.drop(columns = ["X5 latitude", "X6 longitude"]).corr()
plt.figure(figsize=(10, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt=".2f", linewidths=0.5)
plt.title("Matrix de correlacion")
plt.show()
No description has been provided for this image
Análisis: comenta los resultados.

Vemos que como comente antes los atributos con mayor influencia en el precio de la vivienda son 'X3 distance to the nearest MRT station', 'X4 number of convenience stores' y 'X7 num_pois_cercanos'. Aunque observamos un problema y es que hay multicolinearidad entre estas 3 variables lo cual viola uno de los principios de la regresión lineal. Ademas vemos que la transacion date no tiene relacion con el precio de la vivienda.

3. Preprocesado de los datos (1 punto)¶

Una vez analizados los atributos descriptivos, es el momento de prepararlos para que nos sean útiles de cara a predecir valores.

En este apartado:

  • Estandarizaremos los valores de los atributos descriptivos numéricos para que sus escalas no sean muy diferentes.
  • Separaremos el conjunto de datos original en dos subconjuntos: entrenamiento y test.
  • Ejercicio: estandariza todos los atributos descriptivos numéricos, este será el nuevo conjunto de atributos descriptivos con el que trabajaremos desde ahora.
    Sugerencia: utilizad "StandardScaler" de preprocessing.
    In [20]:
    scaler = preprocessing.StandardScaler() # [2]
    # Eliminamos lat/long ya que la relacion que tienen con Y en espacial no lineal
    X_y_standarizado = X_y.drop(columns = ["X5 latitude", "X6 longitude"]).copy()
    X_y_standarizado[variables_descriptivas] = scaler.fit_transform(X_y_standarizado[variables_descriptivas])
    

    Bibliografia [2].

    Ejercicio: separa los atributos descriptivos escalados y la variable objetivo en los subconjuntos de entrenamiento y test.
    Sugerencia: para separar entre train y test podéis usar "train_test_split" de sklearn.
    In [21]:
    X = X_y_standarizado[variables_descriptivas]
    y = X_y_standarizado['Y house price of unit area']
    
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=seed)
    
    Análisis: explica si la decisión de transformar el conjunto de datos (estandarización) antes de realizar la separación del conjunto de datos en los subconjuntos de entrenamiento y test es una buena idea.

    No, no es una buena idea ya que de esa forma la media y la desviacion estandar calculadas para el conjunto entero habran participado en la estandarizacion de los datos de test. Esto no es bueno ya que introducimos un bias en los datos de test y lo que queremos es que sean lo mas independientes posible. Lo correcto hubiera sido dividir entre train y test y luego estandarizamos.

    Análisis: en este ejercicio hemos estandarizado los valores de los atributos descriptivos para que sus escalas no sean muy diferentes. ¿Qué nos aporta estandarizar los atributos descriptivos? ¿hay alguna situación o escenario en la que sea imprescindible?

    Estandarizar los atributos descriptivos es muy importante cuando las escalas son muy diferentes ya que el modelo puede dar mas importancia sino a la variable con valores mas grandes. Ademas la estandarizacion mejora la convergencia de los modelos. Por ejemplo en mi trabajo resuelvo muchos problemas de MILP usando gurobi y vemos un notable cambio en computing time de cuando las variables estan estandarizadas respecto a cuando no.

    4. Reducción de la dimensionalidad (2 puntos)¶

    En este apartado retomaremos el análisis gráfico de distribución de la variable objetivo a lo largo de las muestras del conjunto de datos. En el segundo apartado pudimos observar si las variables descriptivas por separado eran muy prometedoras o no de cara a la predicción. Aquí vamos a intentar determinar si su combinación puede ayudarnos a determinar el precio del inmueble de mejor manera que utilizando los atributos por separado. Con este propósito, vamos a reducir la dimensionalidad del problema a solamente dos atributos, que serán la proyección de los atributos descriptivos originales, y observaremos de qué manera se distribuyen las muestras en función de su valor objetivo.

    Ejercicio:
    • Aplicad el método de reducción de la dimensionalidad Principal Component Analysis (PCA) para reducir a 2 dimensiones el dataset entero con todas las features.
    • Con el objetivo de visualizar si es posible predecir eficientemente el valor de la variable objetivo con este método, generad un gráfico en 2D con el resultado del PCA utilizando una escala de colores que permita distinguir de forma sencilla si los valores altos o bajos se acumulan más o menos en determinadas zonas.

    NOTA: Tened cuidado, no incluyáis la variable objetivo en la reducción de dimensionalidad. Queremos explicar la variable objetivo en función del resto de variables reducidas a dos dimensiones.


    Sugerencia: no es necesario que programéis el algoritmo de PCA, podéis usar la implementación disponible en la librería de scikit-learn.
    In [22]:
    X_pca = X_y_standarizado[variables_descriptivas]
    
    # Aplicamos PCA para reducir la dimensionalidad
    pca = PCA(n_components=2)
    X_pca_transformed = pca.fit_transform(X_pca)
    X_pca_df = pd.DataFrame(X_pca_transformed, columns=['PCA1', 'PCA2'], index=X_pca.index)
    
    # Añadimos el precio de la vivienda para poder visualizarlo
    X_pca_df['Y house price of unit area'] = X_y_standarizado['Y house price of unit area']
    

    Bibliografia [3].

    In [23]:
    plt.figure(figsize=(8, 6))
    sc = plt.scatter(X_pca_df['PCA1'], X_pca_df['PCA2'], c=X_pca_df['Y house price of unit area'], cmap='coolwarm', alpha=0.7)
    plt.colorbar(sc, label='Precio de la vivienda por unidad de area')
    plt.xlabel('PCA1')
    plt.ylabel('PCA2')
    plt.title('PCA proyeccion 2D')
    plt.show()
    
    No description has been provided for this image
    Ejercicio:
    • Repetid la reducción de dimensionalidad, pero en este caso usando TSNE. Podéis encontrar más información acerca de este algoritmo en el siguiente enlace: https://distill.pub/2016/misread-tsne/
    • Al igual que antes, generad un gráfico en 2D con el resultado del TSNE utilizando una escala degradada de colores para la variable objetivo ("y"), con el fin de visualizar si es posible predecir eficientemente el valor de la variable objetivo con este método.

    Sugerencia: no es necesario que programéis el algoritmo TSNE, podéis usar la implementación disponible en la librería de scikit-learn.
    Sugerencia: a parte de especificar el número de componentes, probad a usar los parámetros "learning_rate" y "perplexity".
    In [24]:
    X_tsne = X_y_standarizado[variables_descriptivas].copy()
    
    # Aplicamos TSNE para reducir la dimensionalidad [4]
    tsne = TSNE(n_components=2, learning_rate=251, perplexity=25, random_state=seed)
    X_tsne_transformed = tsne.fit_transform(X_tsne)
    X_tsne_df = pd.DataFrame(X_tsne_transformed, columns=['TSNE1', 'TSNE2'], index=X_y_standarizado.index)
    
    # Añadimos el precio de la vivienda para poder visualizarlo
    X_tsne_df['Y house price of unit area'] = X_y_standarizado['Y house price of unit area']
    

    Bibliografia [4].

    In [25]:
    plt.figure(figsize=(8, 6))
    sc = plt.scatter(X_tsne_df['TSNE1'], X_tsne_df['TSNE2'], c=X_tsne_df['Y house price of unit area'], cmap='coolwarm', alpha=0.7)
    plt.colorbar(sc, label='Precio de la vivienda por unidad de area')
    plt.xlabel('TSNE1')
    plt.ylabel('TSNE2')
    plt.title('TSNE proyeccion 2D')
    plt.show()
    
    No description has been provided for this image
    Análisis: observando los dos gráficos, ¿crees que ha funcionado bien la reducción de dimensionalidad? ¿Crees que será útil para predecir el valor de la variable objetivo? ¿Cuál de los dos métodos ha funcionado mejor? ¿Por qué obtenemos resultados tan diferentes?

    En mi opinion no han conseguido reducir bien la dimensionalidad ya que ambos scatter plots son dispersos no se aprecian ningunas agrupaciones definidas. Creo que no sera util para perdecir el valor de la variable objetivo. Si tuviera que quedarme con uno seria con el TSNE ya que consigue agrupar los puntos un poco mas. Obtenemos resultados tan diferentes porque PCA es linea mientras que TSNE no, ademas de que TSNE tiene varios parametros con los que hemos ido jugando obteniendo graficos cada vez distintos. Un leve cambio en el learning rate o preplexity cambia la gráfica entera.

    Pregunta: ¿qué opinas de TSNE como opción para reducir la dimensionalidad? ¿Qué te parece que sólo tenga el método "fit_transform" pero no tenga "transform"? ¿Conoces alguna otra opción que, con unas prestaciones similares, evite los problemas que tiene TSNE?

    TSNE esta bien para visualizar estructuras no lineales en datos con muchas dimensiones, pero no es adecuado para reducir la dimensionalidad porque no permite transformar nuevos datos (no tiene metodo transform) y es computacionalmente costoso. Como alternativa UMAP [5] ofrece prestaciones similares, siendo más rápido y permite transformar nuevos datos.

    Bibliografia [5].

    5. Conjuntos desbalanceados de datos (1.5 puntos)¶

    En los problemas con variables objetivo discretas, con valores acotados, es muy común encontrar conjuntos de datos muy desbalanceados. En la industria existen múltiples ejemplos, como la detección de fraude o la fuga de clientes. Sin embargo, este inconveniente también está presente en problemas con variable objetivo continua, donde puede apreciarse que la distribución de dicha variable se aleja mucho de ser uniforme.

    El caso del dataset con el que estamos trabajando, tal y como pudiste observar en el apartado de análisis de los datos, no presenta una distribución uniforme, por lo que no existen tantas muestras de inmuebles caros y/o muy baratos en comparación con el número de viviendas de precios intermedios. Este hecho puede llevar a algunos algoritmos de aprendizaje a priorizar la predicción de los valores más comunes, marginando aquellos que no son tan habituales. Con el fin de mitigar esta problemática, existen diferentes algoritmos de balanceo que optan por la eliminación de las muestras más comunes (undersampling) o, por el contrario, tratan de generar nuevas muestras para los valores más escasos de la variable objetivo (oversampling).

    En este caso, os proponemos utilizar un algoritmo que aplica ambas ideas: under y oversampling, redistribuyendo las muestras para que la variable objetivo presente una distribución más uniforme.

    Ejercicio: utiliza smoter, de la biblioteca smogn, para equilibrar la distribución de la variable objetivo. Una vez aplicado el algoritmo, analiza cómo ha quedado la distribución de la variable objetivo y el número de muestras totales que obtenemos.
    In [26]:
    # Aplicamos smoter [6]
    X_y_balanceado = smogn.smoter(data=X_y, y='Y house price of unit area')
    
    plt.figure(figsize=(12, 5))
    plt.subplot(1, 2, 1)
    sns.histplot(X_y['Y house price of unit area'], bins=25, color='blue')
    plt.title("Distribución Original de la variable objetivo")
    plt.xlabel("Precio de vivienda por unidad de area")
    plt.ylabel("Count")
    
    plt.subplot(1, 2, 2)
    sns.histplot(X_y_balanceado['Y house price of unit area'], bins=25, color='blue')
    plt.title("Distribución Balanceada de la variable objetivo")
    plt.xlabel("Precio de vivienda por unidad de area")
    plt.ylabel("Count")
    plt.tight_layout()
    plt.show()
    
    print(f"Numero de samples despues el balanceo: {X_y_balanceado.shape[0]}")
    
    dist_matrix: 100%|##########| 33/33 [00:00<00:00, 165.94it/s]
    synth_matrix: 100%|##########| 33/33 [00:00<00:00, 120.57it/s]
    r_index: 100%|##########| 8/8 [00:00<00:00, 490.99it/s]
    
    No description has been provided for this image
    Numero de samples despues el balanceo: 380
    

    Vemos que ahora hay mas casos cercanos a 120, ademas de una distribución un poco mas uniforme.

    Bibliografia [6].

    Con el objetivo de comprender mejor y de manera visual cómo se generan estas nuevas muestras utilizaremos, a partir de ahora, la descomposición a dos dimensiones que mejor se haya comportado en el apartado anterior.

    Ejercicio: mostrad, mediante un scatter plot en función de las dos componentes a las que anteriormente hemos reducido el dataset, la distribución de los precios de venta del conjunto de datos original y el obtenido al aplicar smoter.
    In [27]:
    # Aplicamos smoter
    X_tsne_df_balanceado = smogn.smoter(data=X_tsne_df, y='Y house price of unit area')
    
    plt.figure(figsize=(12, 5))
    plt.subplot(1, 2, 1)
    sc = plt.scatter(X_tsne_df['TSNE1'], X_tsne_df['TSNE2'], c=X_tsne_df['Y house price of unit area'], cmap='coolwarm', alpha=0.7)
    plt.colorbar(sc, label='Precio de la vivienda por unidad de area')
    plt.xlabel('TSNE1')
    plt.ylabel('TSNE2')
    plt.title('TSNE proyeccion 2D')
    
    plt.subplot(1, 2, 2)
    sc = plt.scatter(X_tsne_df_balanceado['TSNE1'], X_tsne_df_balanceado['TSNE2'], c=X_tsne_df_balanceado['Y house price of unit area'], cmap='coolwarm', alpha=0.7)
    plt.colorbar(sc, label='Precio de la vivienda por unidad de area')
    plt.xlabel('TSNE1')
    plt.ylabel('TSNE2')
    plt.title('TSNE proyeccion 2D')
    plt.show()
    
    dist_matrix: 100%|##########| 33/33 [00:00<00:00, 227.08it/s]
    synth_matrix: 100%|##########| 33/33 [00:00<00:00, 267.08it/s]
    r_index: 100%|##########| 8/8 [00:00<00:00, 1036.56it/s]
    
    No description has been provided for this image
    Análisis: comentad los resultados.

    Vemos que el punto rojo a la altura de TSNE1 = 20 que antes era un outlier ahora no es un caso aislado sino que hay mas puntos rojos a su alrededor. Por lo cual vemos que smoter ha cumplido su funcion y ha conseguido que una minoría de casos quede mas representada, para que asi a la hora de entrenar el modelo este se ajuste tambien a los casos minoritarios.

    6. Búsqueda y combinación de nuevos conjuntos de datos (1 punto)¶

    En este apartado os animamos a que busquéis de manera libre un nuevo conjunto de datos que podáis combinar de alguna forma (a través de alguno o algunos atributos comunes) con el conjunto de datos inicial para enriquecer más aún la información que tenemos de cada vivienda de cara a estimar mejor su precio.

    Es inprescindible que, una vez tengáis el conjunto de datos ampliado con el cálculo de vuestros nuevos atributos descriptivos procedentes del nuevo dataset, reejecutéis todo el Notebook, a excepción de la carga inicial del conjunto de datos Real Estate Valuation, para comprobar el efecto de dicha ampliación.

    Bibliografía¶

    1. https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.BallTree.html
    2. https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html
    3. https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html
    4. https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html
    5. https://umap-learn.readthedocs.io/en/latest/basic_usage.html
    6. https://pypi.org/project/smogn/